/*****************************************************************************
 *   Copyright 2005 Tim A Wang
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 *
 ****************************************************************************/
package com.jb.web.bean.inspector;

import com.jb.framework.util.BeanUtil;
import com.jb.framework.util.PropUtil;
import com.jb.util.StringUtil;
import com.jb.web.application.WebBeanz;
import com.jb.web.bean.Form;
import com.jb.web.bean.HtmlItem;
import com.jb.web.bean.UIComponentImpl;
import com.jb.web.bean.ValueChangeEvent;
import com.jb.web.context.WebContext;

import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.jb.faces.context.FacesContext;

import com.jb.util.ClassUtil;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


/**
 * This is the form that allows you to edit a java bean.
 */
public class PropertyInspector extends Form {
    private static final long serialVersionUID = 123123L;
    private static Log log = LogFactory.getLog(PropertyInspector.class.getName());
    
    private UIComponentImpl component;
    private static String ID = "id";
    private HashMap hashedCategories = new HashMap(); 
    private List flags = new ArrayList();

    private void _init() {
        clearChildren();
        hashedCategories.clear();
    }
    
    public void processDecodes(FacesContext fCtx) {
        if (log.isDebugEnabled()) {
            log.debug("into processDecodes");
        }
        _init();
      
        WebContext ctx = (WebContext)fCtx;
        String compId = ctx.getRequest().getParameter(ID);
        component = ctx.getSession().getDesignComponentById(compId);
        if(component == null) {
            ctx.addErrorMessage(this, "No component defined" + compId);
            return;
        }
        if(!ctx.isRunTime())
            ctx.getSession().setSelected(component);
        
        setupItems(ctx);
        
        if (log.isDebugEnabled()) {
            log.debug("exit processDecodes");
        }
    }

    public void processUpdates(FacesContext ctx) {
        if (log.isDebugEnabled()) {
            log.debug("into processUpdates");
        }

        WebContext wctx = (WebContext)ctx;
        if (isMyEvent(wctx) && (component != null) && 
            component instanceof WebBeanz) {
            setPropertiesValue(wctx); //sets values
            component.getRoot().save(wctx);
        }
        super.processUpdates(ctx);
        if (log.isDebugEnabled()) {
            log.debug("exit processUpdates");
        }
    }

    public boolean showDefaultButton() {
        return true;
    }

    public void updateView(WebContext ctx) {
        if (log.isDebugEnabled()) {
            log.debug("into updateView");
        }
        super.updateView(ctx);
        if(component==null)
            return;
        setAction(ctx.getRequestURL() + "?requestMode=dialog&id=" + ctx.getRequest().getParameter("id"));
        bindComponent(component);
        if (log.isDebugEnabled()) {
            log.debug("exit updateView");
        }
    }

    public void valueChanged(ValueChangeEvent event) {
        if (log.isDebugEnabled()) {
            log.debug("exit valueChanged");
        }
        String pName = event.getPropertyName();
        Object pValue = event.getNewValue();

        if (component != null) {
            component.setPropertyValue(pName, pValue);
        }
        if (log.isDebugEnabled()) {
            log.debug("exit valueChanged");
        }
    }

    private void setupItems(WebContext ctx) {
        if (log.isDebugEnabled()) {
            log.debug("into setupItems");
        }
        if (getChildrenByType(PropertyField.class).size() != 0) {
            return;
        }
        setStyleClass("jbPIForm");
        try {
            bindComponent(component);
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error(this, e);
            ctx.addErrorMessage(this, e.getMessage());
        } finally {
            clearForm();
        }
        if (log.isDebugEnabled()) {
            log.debug("exit setupItems");
        }
    }

    private void bindComponent(Object source) {
        if (log.isDebugEnabled()) {
            log.debug("into bindComponent");
        }
        if (source == null) {
            if (log.isErrorEnabled()) {
                log.error("Failed to bind source as it is null");
                Thread.dumpStack();
            }
            return;
        }
        Iterator it = PropUtil.getPropertyNames(source.getClass()).iterator();
        try {
            component = (UIComponentImpl)source;
            while (it.hasNext()) {
                String pName = (String)it.next();
                if (DefaultPropertyEditor.isPublished(pName, component)) {
                    Object pValue = PropertyUtils.getProperty(component, pName);
                    String category = DefaultPropertyEditor.getPropertyCategory(component, pName);
                    setValue(component, category, pName, (pValue == null) ? "" : pValue);
                }
            }
        } catch (Exception e) {
            if (log.isErrorEnabled())
                log.error(this, e);
        }
        if (log.isDebugEnabled()) {
            log.debug("exit bindComponent");
        }

    }

    private void setPropertiesValue(WebContext ctx) {
        //WebBeanz wb = component;
        Enumeration hm = ctx.getRequest().getParameterNames();
        while (hm.hasMoreElements()) {
            String pName = (String)hm.nextElement();

            try {
                String pValue = ctx.getRequest().getParameter(pName);
                component.setPropertyValue(pName, pValue);
            } catch (Exception e) {
                if (log.isErrorEnabled())
                    log.error(this, e);
            }
        }
        component.valueChanged(ctx);
    }

    private void setValue(UIComponentImpl bean, 
                          String category, String propertyName, 
                          Object propertyValue) throws Exception {

        PropertyField fItem = (PropertyField)getChildByNameType(getId() + "_" + propertyName, 
                                              PropertyField.class);

        if (fItem == null) {
            fItem = (PropertyField)ClassUtil.createInstance(PropertyField.class);
            fItem.setId(getId() + "_" + propertyName);

            fItem.setWidth("100%");
            fItem.setModelBean(bean);
            fItem.setPropertyValue(propertyValue);

            addItem(fItem, category);

            configFieldItem(fItem, propertyName, propertyValue);

        } else if (!getId().equals(fItem.getForm().getId())) {
            addItem(fItem, category);
        }

        fItem.setValue(propertyValue.toString());
    }

    
    public void toggleCategory(String groupName) {
        Category category =(Category) hashedCategories.get(groupName);
        if(category!=null)
            category.visible=!category.visible;
        else
            return;
        
        if(!category.visible) {
           if(!flags.contains(groupName))
            flags.add(groupName);
        }else {
            if(flags.contains(groupName))
                flags.remove(groupName);
        }
    }
    
    public static class Category{
        public Category(String name, List items, boolean visible) {
            this.name=name;
            this.items=items;
            this.visible=visible;
        }
        
        public String name;
        public List items;
        public boolean visible;
        
    }

    public Map getCategories() {
        return hashedCategories;
    }

    private void addItem(HtmlItem newItem, String categoryName) {
        if (getComponentByName(newItem.getId()) != null) {
            return;
        }

        Category category = (Category)hashedCategories.get(categoryName);
        
        if (category == null) {
            category = new Category(categoryName, new ArrayList(), !flags.contains(categoryName));
            hashedCategories.put(categoryName, category);
        }
        
        if (!category.items.contains(newItem))
        {
            category.items.add(newItem);
        }

        addChild(newItem);
    }

    private void clearForm() {
        for (int i = 0; i < getChildCount(); i++) {
            HtmlItem fItem = (HtmlItem)getComponentAt(i);
            if (fItem instanceof PropertyField) {
                fItem.setValue("");
            }
        }
    }

    private void configFieldItem(PropertyField fItem, String propertyName, 
                                 Object propertyValue) {
        fItem.setLabel(StringUtil.itemNameToLabel(propertyName));
        fItem.setClientId(propertyName);
        registerPropertyEditor(fItem, propertyName, propertyValue);
    }

    private void registerPropertyEditor(PropertyField fItem, 
                                        String propertyName, 
                                        Object propertyValue) {
        Object object = fItem.getModelBean();

        if (object == null) {
            return;
        }

        Class propertyClass = object.getClass();

        if (propertyValue instanceof Boolean && 
            !"rendered".equalsIgnoreCase(propertyName)) {
            DefaultPropertyEditor.addPropertyEditor(propertyClass, 
                                                    propertyName, 
                                                    new OptionPropertyEditor("seeded:boolean"));
        }
    }
}
